#include "str.h"

#include "mem.h"
#include "assert.h"

#include <string.h>

// For a string: Interface
// 1  2  3  4  5  6  7  8  9  10  <-- positive index
// I  n  t  e  r  f  a  c  e
//-9 -8 -7 -6 -5 -4 -3 -2 -1  0   <-- negative index

// For positive i [1, len]
// For negative i [-len, -1]
// [len+1] and [0] point to the same position
#define idx(i, len) ((i) <= 0 ? (i)+len : (i)-1)

#define convert(s, i, j) { \
    assert(s); int len = strlen(s); \
    i = idx(i, len); j = idx(j, len); \
    if (i > j) {int t = i; i = j; j = t;} \
    assert(i >= 0 && j <= len); \
}

char *Str_sub(const char *s, int i, int j)
{
    convert(s, i, j); // After [i, j)
    
    char *p, *str;
    p = str = ALLOC(j - i + 1);
    while (i < j)
        *p++ = s[i++];
    *p = '\0';
    return str;
}

char *Str_dup(const char *s, int i, int j, int n)
{
    convert(s, i, j);
    assert(n >= 0);

    char *p, *str;
    p = str = ALLOC(n*(j-i) + 1);

    if (j - i > 0) {
        while (n-- > 0) {
            for (int k = i; k < j; k++)
               *p++ = s[k]; 
        }
    }
    *p = '\0';
    return str;
}

char *Str_cat(const char *s1, int i1, int j1, const char *s2, int i2, int j2)
{
    convert(s1, i1, j1);
    convert(s2, i2, j2);

    char *p, *str;
    p = str = ALLOC(j1-i1 + j2-i2 + 1);
    while (i1 < j1)
        *p++ = s1[i1++];
    while (i2 < j2)
        *p++ = s2[i2++];
    *p = '\0';
    return str;
}

// s, i, j, s, i, j, NULL
char *Str_catv(const char *s, ...)
{
    const char *save = s;
    va_list ap;
    va_start(ap, s);

    int len = 0;
    while (s) {
        int i = va_arg(ap, int);
        int j = va_arg(ap, int);
        convert(s, i, j);
        len += j - i;

        s = va_arg(ap, const char*);
    }
    va_end(ap);
    s = save;

    char *p, *str;
    p = str = ALLOC(len + 1);

    va_start(ap, s);
    while (s) {
        int i = va_arg(ap, int);
        int j = va_arg(ap, int);
        convert(s, i, j);

        while (i < j)
            *p++ = s[i++];

        s = va_arg(ap, const char*);
    }
    va_end(ap);

    *p = '\0';
    return str;
}

char *Str_reverse(const char *s, int i, int j)
{
    convert(s, i, j);

    char *str, *p;
    p = str = ALLOC(j - i + 1);

    while (j > i)
        *p++ = s[--j];
    *p = '\0';
    return str;
}

char *Str_map(const char *s, int i, int j, const char *from, const char *to)
{
    static char map[256] = {0};

    if (from && to) {
        for (int c = 0; c < (int)sizeof(map); c++)
            map[c] = c;
        while (*from && *to)
            map[(unsigned char)*from++] = *to++;
        assert(*from == 0 && *to == 0);

    } else {
        assert(from == NULL && to == NULL && s);
        assert(map['a']);
    }

    if (s) {
        char *p, *str;
        convert(s, i, j);
        p = str = ALLOC(j - i + 1);

        while (i < j)
            *p++ = map[(unsigned char)s[i++]];
        *p = '\0';
        return str;

    } else {
        return NULL;
    }
}

// Transform to positive index, start from 1
int Str_pos(const char *s, int i)
{
    assert(s);

    int len = strlen(s);
    i = idx(i, len);
    assert(i >= 0 && i <= len);
    return i + 1;
}

int Str_len(const char *s, int i, int j)
{
    convert(s, i, j);
    return j - i;
}

int Str_cmp(const char *s1, int i1, int j1, const char *s2, int i2, int j2)
{
    convert(s1, i1, j1);
    convert(s2, i2, j2);

    s1 += i1;
    s2 += i2;

    if (j1-i1 < j2-i2) {
        int cond = strncmp(s1, s2, j1-i1);
        return cond == 0 ? -1 : cond;
    } else if (j1-i1 > j2-i2) {
        int cond = strncmp(s1, s2, j2-i2);
        return cond == 0 ? 1 : cond;
    } else {
        return strncmp(s1, s2, j1-i1);
    }
}

int Str_chr(const char *s, int i, int j, int c)
{
    convert(s, i, j);
    
    for (; i < j; i++) {
        if (s[i] == c)
            return i + 1;
    }
    return 0;
}

int Str_rchr(const char *s, int i, int j, int c)
{
    convert(s, i, j);

    while (j > i) {
        if (s[--j] == c)
            return j + 1;
    }
    return 0;
}

int Str_upto(const char *s, int i, int j, const char *set)
{
    convert(s, i, j);

    for (; i < j; i++) {
        if (strchr(set, s[i]))
            return i + 1;
    }
    return 0;
}

int Str_rupto(const char *s, int i, int j, const char *set)
{
    convert(s, i, j);

    while (j > i) {
        if (strchr(set, s[--j]))
            return j + 1;
    }
    return 0;
}

int Str_find(const char *s, int i, int j, const char *str)
{
    convert(s, i, j);
    assert(str);

    int len = strlen(str);
    if (len == 0) {
        return i + 1; // Always find an empty string
    } else if (len == 1) {
        for (; i < j; i++) {
            if (s[i] == str[0])
                return i + 1;
        }
    } else {
        for (; i + len <= j; i++) {
            if (strncmp(&s[i], str, len) == 0)
                return i + 1;
        }
    }
    return 0;
}

int Str_rfind(const char *s, int i, int j, const char *str)
{
    convert(s, i, j);
    assert(str);

    int len = strlen(str);
    if (len == 0) {
        return i + 1; // Always find an empty string
    } else if (len == 1) {
        while (j > i) {
            if (s[--j] == str[0])
                return i + 1;
        }
    } else {
        for (; j - len >= i; j--) {
            if (strncmp(&s[j-len], str, len) == 0)
                return i + 1;
        }
    }
    return 0;
}

int Str_any(const char *s, int i, const char *set)
{
    assert(s);
    assert(set);

    int len = strlen(s);
    i = idx(i, len);
    assert(i >= 0 && i <= len);
    
    if (i < len && strchr(set, s[i]))
        return i+1 + 1;
    else
        return 0;
}

int Str_many(const char *s, int i, int j, const char *set)
{
    assert(set);
    convert(s, i, j);

    if (i < j && strchr(set, s[i])) {
        do {
            i++;
        } while (i < j && strchr(set, s[i]));
        return i+1; // i is the first position not in set

    } else {
        return 0;
    }
}

int Str_rmany(const char *s, int i, int j, const char *set)
{
    assert(set);
    convert(s, i, j);

    if (j > i && strchr(set, s[j-1])) {
        do {
            j--;
        } while (j > i && strchr(set, s[j]));
        return j+1 + 1; // j+1 is the last position not in set

    } else {
        return 0;
    }
}

int Str_match(const char *s, int i, int j, const char *str)
{
    assert(str);
    convert(s, i, j);

    int len = strlen(str);
    if (len == 0) {
        return i+1;
    } else if (len == 1) {
        if (i < j && s[i] == str[0])
            return i+1 + 1; // i+1 is the first not matched postion
    } else if (i+len <= j && strncmp(&s[i], str, len) == 0) {
        return i+len + 1; // i+len is the first not matched postion
    }
    return 0;
}

int Str_rmatch(const char *s, int i, int j, const char *str)
{
    assert(str);
    convert(s, i, j);

    int len = strlen(str);
    if (len == 0) {
        return j+1;
    } else if (len == 1) {
        if (j > i && s[j-1] == str[0])
            return j; // j-1 is the start matching position
    } else if (j-len >= i && strncmp(&s[j-len], str, len) == 0) {
        return j-len + 1; // j-len is the start matching position
    }
    return 0;
}

void Str_fmt(int code, va_list_box *box,
             int put(int c, void *cl), void *cl,
             unsigned char flags[256], int width, int precision)
{
    (void)code;

    assert(box && flags);

    const char *s = va_arg(box->ap, const char *);
    int i = va_arg(box->ap, int);
    int j = va_arg(box->ap, int);

    convert(s, i, j);
    Fmt_puts(s+i, j-i, put, cl, flags, width, precision);
}

